import json
import time

import numpy as np
import User.data_process as data_process  # 导入数据处理脚本，注意位置
import operator
from tqdm import *

from User.models import Rating


def init_vector(train_data, vector_len):
    """
    初始化user和item的向量
    train_data: 训练数据
    vector_len: 向量的长度
    return:
        user vector and item vector
    """
    init_user_vec = {}
    init_item_vec = {}
    for data_instance in train_data:
        userID, itemID, _ = data_instance
        init_user_vec[userID] = np.random.randn(vector_len)
        init_item_vec[itemID] = np.random.randn(vector_len)
    return init_user_vec, init_item_vec


def lfm_train(train_data, F, alpha, learning_rate, step):
    """
    采用梯度下降，不断更新迭代 user_vector and item_vector
    train_data: 训练数据集
    F:     隐特征数
    alpha:  正则化系数
    learning_rate: 学习率
    step: 迭代轮次
    return:
        dict  {userID: [user_vector]}
        dict  {itemID: [user_vector]}
    """
    user_vec, item_vec = init_vector(train_data, F)  # 随机初始化user and item vector
    for _ in tqdm(range(step), desc="训练进度： "):  # 训练轮次
        for data_instance in (train_data):
            userID, itemID, label = data_instance
            user_vector, item_vector = user_vec[userID], item_vec[itemID]  # user vector, item vector
            vector_dot = np.dot(user_vector, item_vector) / (
                    np.linalg.norm(user_vector) * np.linalg.norm(item_vector))  # 向量点积（用户对物品的喜爱程度）,余弦距离
            loss = label - vector_dot  # loss
            user_vector += np.multiply(learning_rate,
                                       (loss * item_vector - alpha * user_vector))  # user_vector 不断更新（根据梯度下降公式），偏导
            item_vector += np.multiply(learning_rate,
                                       (loss * user_vector - alpha * item_vector))  # item_vector 不断更新（根据梯度下降公式）
        learning_rate = learning_rate * 0.95  # 学习率衰减
    return user_vec, item_vec


def top_n_item(user_vec, item_vec, userid):
    """
    计算某用户最喜爱的top_n个物品
    user_vec:   用户向量
    item_vec:   物品向量
    userid:     用户ID
    return:
        a list: [(item, score), (item1, score1),,,]
    """
    top_n = 20
    start = time.time()
    list = Rating.objects.values_list('userId', 'gameId').filter(userId=userid)
    print("获取特定用户玩过的游戏时间%f" % (time.time() - start))
    if userid not in user_vec:
        return []
    item_score = {}  # 存放用户对某物品的喜爱程度
    top_list = []  # top_n喜欢的物品
    user_vector = user_vec[userid]  # 获取指定user对应的vector
    for itemID in item_vec:  # 遍历所有的item
        # if list.filter(gameId=itemID).exists():
        #     print("exists要花很长时间吗%f"%(time.time()-start))
        #     continue
        #     pass
        item_vector = item_vec[itemID]  # 拿到item对应的vector
        vector_dot = np.dot(user_vector, item_vector) / (
                np.linalg.norm(user_vector) * np.linalg.norm(item_vector))  # 计算user和item1的相似度（喜爱程度），余弦距离
        item_score[itemID] = vector_dot  # 将该用户对所有的物品喜爱程度写入字典中
    print("遍历item要花很长时间吗%f" % (time.time() - start))
    # 以喜爱程度排序，取前top_n个用户最喜欢的item，返回
    i = 0
    for item_i in sorted(item_score.items(), key=operator.itemgetter(1), reverse=True):
        itemID, score = item_i[0], round(item_i[1], 3)
        if list.filter(gameId=itemID).exists():
            continue
            pass
        i = i + 1
        if (i == top_n): break
        top_list.append((itemID, score))  # 添加到最终的列表
    return top_list


def print_top_result(train_data, userid, top_n):
    """
    打印某用户以前看过的物品和算法推荐的物品
    :param train_data: 用户之前喜欢的item
    :param userid:     用户id
    :param recom_list: 推荐的item
    :return:
    """
    item_info = data_process.get_item_info("../data/movies.csv")  # item的信息
    print("----------------------------User clicked item----------------------------")
    for data_instance in train_data:  # 遍历数据集
        userID_tem, itemID, label = data_instance
        if userID_tem == userid and label == 1:  # 若打印某user喜爱的物品（正样本）
            print(f"[User clicked items] itemID : {itemID} ; item_info : {item_info[itemID]} ")

    print("----------------------------Recommendation result of LFM algorithm----------------------------")

    for item_i in top_n:
        print(f"[Recommended items] itemID : {item_i[0]} ; item_info : {item_info[item_i[0]]} ; 喜爱程度： {item_i[1]}")


def get_train(object):
    start = time.time()
    train_data = data_process.get_train_data_from_Queryset(object)
    print("获取数据，用时{}".format(time.time() - start))
    user_vec, item_vec = lfm_train(train_data, 32, 0.01, 0.1, 10)  # 训练

    print("训练数据，用时{}".format(time.time() - start))
    for u in user_vec.keys():
        user_vec[u] = user_vec[u].tolist()
        pass
    json_user_str = json.dumps(user_vec)  # 如果想在json里看清楚些，设置参数indent=4
    with open('user_vec.json', 'w') as user_file:
        user_file.write(json_user_str)

    for i in item_vec.keys():
        item_vec[i] = item_vec[i].tolist()
        pass
    json_item_str = json.dumps(item_vec)  # 如果想在json里看清楚些，设置参数indent=4
    with open('game_vec.json', 'w') as item_file:
        item_file.write(json_item_str)


def getlist(object, id):
    # get_train(object)
    with open('game_vec.json', 'r') as item_file:
        for line in item_file:
            item_vec1 = json.loads(line)
        pass
    with open('user_vec.json', 'r') as user_file:
        for line in user_file:
            user_vec1 = json.loads(line)
        pass

    userID = str(id)  # 以用户ID‘2’为例，查看其推荐结果
    # print(user_vec1[userID])
    top_result = top_n_item(user_vec1, item_vec1, userID)

    return top_result


if __name__ == '__main__':
    train_data = data_process.get_train_data("../data/ratings.csv")
    user_vec, item_vec = lfm_train(train_data, 32, 0.01, 0.1, 1)  # 训练
    userID = '1'  # 以用户ID‘2’为例，查看其推荐结果
    top_result = top_n_item(user_vec, item_vec, userID)
    print_top_result(train_data, userID, top_result)
